]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
swipeTracker: Port to use ClutterPanGesture
authorJonas Dreßler <verdre@v0yd.nl>
Thu, 31 Jul 2025 09:42:26 +0000 (11:42 +0200)
committerCarlos Garnacho <carlosg@gnome.org>
Thu, 28 Aug 2025 17:14:06 +0000 (19:14 +0200)
The new ClutterGesture subclass ClutterPanGesture fits the SwipeTracker
quite well. API wise the biggest difference to the TouchSwipeGesture it
was using before is that ClutterPanGesture has its own internal event
history to keep track of deltas and to calculate a velocity in the end.
That means we need to give it slighty special treatment right now, but
we'll improve that with the next commits.

So replace the internal TouchSwipeGesture and make the SwipeTracker use
ClutterPanGesture directly.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2853>

js/ui/swipeTracker.js

index d3df86ab3176114a4185a9b71c66a997697e1353..4720ee10f4fee847937a0f518f60eccdd9541435 100644 (file)
@@ -208,97 +208,6 @@ const TouchpadSwipeGesture = GObject.registerClass({
     }
 });
 
-const TouchSwipeGesture = GObject.registerClass({
-    Properties: {
-        'distance': GObject.ParamSpec.double(
-            'distance', null, null,
-            GObject.ParamFlags.READWRITE,
-            0, Infinity, 0),
-        'orientation': GObject.ParamSpec.enum(
-            'orientation', null, null,
-            GObject.ParamFlags.READWRITE,
-            Clutter.Orientation, Clutter.Orientation.HORIZONTAL),
-    },
-    Signals: {
-        'begin':  {param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE]},
-        'update': {param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE]},
-        'end':    {param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE]},
-        'cancel': {param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE]},
-    },
-}, class TouchSwipeGesture extends Clutter.GestureAction {
-    _init(allowedModes, nTouchPoints, thresholdTriggerEdge) {
-        super._init();
-        this.set_n_touch_points(nTouchPoints);
-        this.set_threshold_trigger_edge(thresholdTriggerEdge);
-
-        this._allowedModes = allowedModes;
-        this._distance = global.screen_height;
-        this._lastPosition = 0;
-    }
-
-    get distance() {
-        return this._distance;
-    }
-
-    set distance(distance) {
-        if (this._distance === distance)
-            return;
-
-        this._distance = distance;
-        this.notify('distance');
-    }
-
-    vfunc_gesture_prepare(actor) {
-        if (!super.vfunc_gesture_prepare(actor))
-            return false;
-
-        if ((this._allowedModes & Main.actionMode) === 0)
-            return false;
-
-        let time = this.get_last_event(0).get_time();
-        let [xPress, yPress] = this.get_press_coords(0);
-        let [x, y] = this.get_motion_coords(0);
-        const [xDelta, yDelta] = [x - xPress, y - yPress];
-        const swipeOrientation = Math.abs(xDelta) > Math.abs(yDelta)
-            ? Clutter.Orientation.HORIZONTAL : Clutter.Orientation.VERTICAL;
-
-        if (swipeOrientation !== this.orientation)
-            return false;
-
-        this._lastPosition =
-            this.orientation === Clutter.Orientation.VERTICAL ? y : x;
-
-        this.emit('begin', time, xPress, yPress);
-        return true;
-    }
-
-    vfunc_gesture_progress(_actor) {
-        let [x, y] = this.get_motion_coords(0);
-        let pos = this.orientation === Clutter.Orientation.VERTICAL ? y : x;
-
-        let delta = pos - this._lastPosition;
-        this._lastPosition = pos;
-
-        let time = this.get_last_event(0).get_time();
-
-        this.emit('update', time, -delta, this._distance);
-
-        return true;
-    }
-
-    vfunc_gesture_end(_actor) {
-        let time = this.get_last_event(0).get_time();
-
-        this.emit('end', time, this._distance);
-    }
-
-    vfunc_gesture_cancel(_actor) {
-        let time = Clutter.get_current_event_time();
-
-        this.emit('cancel', time, this._distance);
-    }
-});
-
 const ScrollGesture = GObject.registerClass({
     Properties: {
         'enabled': GObject.ParamSpec.boolean(
@@ -467,47 +376,41 @@ export const SwipeTracker = GObject.registerClass({
         this._history = new EventHistory();
         this._reset();
 
+        this._panGesture = new Clutter.PanGesture({
+            min_n_points: params.allowDrag ? 1 : GESTURE_FINGER_COUNT,
+            max_n_points: params.allowDrag ? 0 : GESTURE_FINGER_COUNT,
+        });
+        this._panGesture.connect('may-recognize', () => {
+            return (this._allowedModes & Main.actionMode) !== 0;
+        });
+        this._panGesture.connect('recognize', this._beginPanGesture.bind(this));
+        this._panGesture.connect('pan-update', this._updatePanGesture.bind(this));
+        this._panGesture.connect('end', this._endPanGesture.bind(this));
+        this._panGesture.connect('cancel', this._cancelPanGesture.bind(this));
+        this.bind_property('enabled', this._panGesture, 'enabled', 0);
+        this.bind_property_full('orientation',
+            this._panGesture, 'pan-axis',
+            GObject.BindingFlags.SYNC_CREATE,
+            (bind, source) => [
+                true,
+                source === Clutter.Orientation.HORIZONTAL
+                    ? Clutter.PanAxis.X : Clutter.PanAxis.Y,
+            ],
+            null);
+        actor.add_action_full("SwipeTracker", Clutter.EventPhase.BUBBLE, this._panGesture);
+
         this._touchpadGesture = new TouchpadSwipeGesture(allowedModes);
-        this._touchpadGesture.connect('begin', this._beginGesture.bind(this));
-        this._touchpadGesture.connect('update', this._updateGesture.bind(this));
+        this._touchpadGesture.connect('begin', this._beginTouchpadGesture.bind(this));
+        this._touchpadGesture.connect('update', this._updateTouchpadGesture.bind(this));
         this._touchpadGesture.connect('end', this._endTouchpadGesture.bind(this));
         this.bind_property('enabled', this._touchpadGesture, 'enabled', 0);
         this.bind_property('orientation', this._touchpadGesture, 'orientation',
             GObject.BindingFlags.SYNC_CREATE);
 
-        this._touchGesture = new TouchSwipeGesture(allowedModes,
-            GESTURE_FINGER_COUNT,
-            Clutter.GestureTriggerEdge.AFTER);
-        this._touchGesture.connect('begin', this._beginTouchSwipe.bind(this));
-        this._touchGesture.connect('update', this._updateGesture.bind(this));
-        this._touchGesture.connect('end', this._endTouchGesture.bind(this));
-        this._touchGesture.connect('cancel', this._cancelTouchGesture.bind(this));
-        this.bind_property('enabled', this._touchGesture, 'enabled', 0);
-        this.bind_property('orientation', this._touchGesture, 'orientation',
-            GObject.BindingFlags.SYNC_CREATE);
-        this.bind_property('distance', this._touchGesture, 'distance', 0);
-        global.stage.add_action_full('swipe', Clutter.EventPhase.CAPTURE, this._touchGesture);
-
-        if (params.allowDrag) {
-            this._dragGesture = new TouchSwipeGesture(allowedModes, 1,
-                Clutter.GestureTriggerEdge.AFTER);
-            this._dragGesture.connect('begin', this._beginGesture.bind(this));
-            this._dragGesture.connect('update', this._updateGesture.bind(this));
-            this._dragGesture.connect('end', this._endTouchGesture.bind(this));
-            this._dragGesture.connect('cancel', this._cancelTouchGesture.bind(this));
-            this.bind_property('enabled', this._dragGesture, 'enabled', 0);
-            this.bind_property('orientation', this._dragGesture, 'orientation',
-                GObject.BindingFlags.SYNC_CREATE);
-            this.bind_property('distance', this._dragGesture, 'distance', 0);
-            actor.add_action_full('drag', Clutter.EventPhase.CAPTURE, this._dragGesture);
-        } else {
-            this._dragGesture = null;
-        }
-
         if (params.allowScroll) {
             this._scrollGesture = new ScrollGesture(actor, allowedModes);
-            this._scrollGesture.connect('begin', this._beginGesture.bind(this));
-            this._scrollGesture.connect('update', this._updateGesture.bind(this));
+            this._scrollGesture.connect('begin', this._beginTouchpadGesture.bind(this));
+            this._scrollGesture.connect('update', this._updateTouchpadGesture.bind(this));
             this._scrollGesture.connect('end', this._endTouchpadGesture.bind(this));
             this.bind_property('enabled', this._scrollGesture, 'enabled', 0);
             this.bind_property('orientation', this._scrollGesture, 'orientation',
@@ -577,28 +480,32 @@ export const SwipeTracker = GObject.registerClass({
 
     _interrupt() {
         this.emit('end', 0, this._cancelProgress);
+        this._panGesture.cancel();
         this._reset();
     }
 
-    _beginTouchSwipe(gesture, time, x, y) {
-        if (this._dragGesture)
-            this._dragGesture.cancel();
-
-        this._beginGesture(gesture, time, x, y);
-    }
-
-    _beginGesture(gesture, time, x, y) {
+    _beginGesture(x, y) {
         if (this._state === State.SCROLLING)
             return;
 
-        this._history.append(time, 0);
-
         const rect = new Mtk.Rectangle({x, y, width: 1, height: 1});
         let monitor = global.display.get_monitor_index_for_rect(rect);
 
         this.emit('begin', monitor);
     }
 
+    _beginPanGesture(panGesture) {
+        const coords = panGesture.get_begin_centroid();
+
+        this._beginGesture(coords.x, coords.y);
+    }
+
+    _beginTouchpadGesture(gesture, time, x, y) {
+        this._history.append(time, 0);
+
+        this._beginGesture(x, y);
+    }
+
     _findClosestPoint(pos) {
         const distances = this._snapPoints.map(x => Math.abs(x - pos));
         const min = Math.min(...distances);
@@ -645,27 +552,40 @@ export const SwipeTracker = GObject.registerClass({
         return [this._snapPoints[lowerIndex], this._snapPoints[upperIndex]];
     }
 
-    _updateGesture(gesture, time, delta, distance) {
+    _updateGesture(delta, distance) {
         if (this._state !== State.SCROLLING)
             return;
 
-        if ((this._allowedModes & Main.actionMode) === 0 || !this.enabled) {
-            this._interrupt();
-            return;
-        }
-
         if (this.orientation === Clutter.Orientation.HORIZONTAL &&
             Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
             delta = -delta;
 
         this._progress += delta / distance;
-        this._history.append(time, delta);
 
         this._progress = Math.clamp(this._progress, ...this._getBounds(this._initialProgress));
 
         this.emit('update', this._progress);
     }
 
+    _updatePanGesture(panGesture) {
+        const deltaVec = panGesture.get_delta();
+        const delta = this.orientation === Clutter.Orientation.HORIZONTAL
+            ? -deltaVec.get_x()
+            : -deltaVec.get_y();
+
+        this._updateGesture(delta, this._distance);
+    }
+
+    _updateTouchpadGesture(gesture, time, delta, distance) {
+        if ((this._allowedModes & Main.actionMode) === 0 || !this.enabled) {
+            this._interrupt();
+            return;
+        }
+
+        this._history.append(time, delta);
+        this._updateGesture(delta, distance);
+    }
+
     _getEndProgress(velocity, distance, isTouchpad) {
         if (this._cancelled)
             return this._cancelProgress;
@@ -698,15 +618,7 @@ export const SwipeTracker = GObject.registerClass({
         return this._snapPoints[index];
     }
 
-    _endTouchGesture(_gesture, time, distance) {
-        this._endGesture(time, distance, false);
-    }
-
-    _endTouchpadGesture(_gesture, time, distance) {
-        this._endGesture(time, distance, true);
-    }
-
-    _endGesture(time, distance, isTouchpad) {
+    _endGesture(velocity, distance, isTouchpad) {
         if (this._state !== State.SCROLLING)
             return;
 
@@ -715,9 +627,6 @@ export const SwipeTracker = GObject.registerClass({
             return;
         }
 
-        this._history.trim(time);
-
-        let velocity = this._history.calculateVelocity();
         const endProgress = this._getEndProgress(velocity, distance, isTouchpad);
 
         velocity /= distance;
@@ -736,12 +645,33 @@ export const SwipeTracker = GObject.registerClass({
         this.emit('end', duration, endProgress);
     }
 
-    _cancelTouchGesture(_gesture, time, distance) {
+    _endPanGesture(panGesture) {
+        const velocity = panGesture.get_velocity();
+        const v = this.orientation === Clutter.Orientation.HORIZONTAL
+            ? -velocity.get_x()
+            : -velocity.get_y();
+
+        this._endGesture(v, this._distance, false);
+    }
+
+    _endTouchpadGesture(gesture, time, distance) {
+        if ((this._allowedModes & Main.actionMode) === 0 || !this.enabled) {
+            this._interrupt();
+            return;
+        }
+
+        this._history.trim(time);
+        const velocity = this._history.calculateVelocity();
+
+        this._endGesture(velocity, distance, true);
+    }
+
+    _cancelPanGesture() {
         if (this._state !== State.SCROLLING)
             return;
 
         this._cancelled = true;
-        this._endGesture(time, distance, false);
+        this._endGesture(0, this._distance, false);
     }
 
     /**
@@ -773,9 +703,9 @@ export const SwipeTracker = GObject.registerClass({
             delete this._touchpadGesture;
         }
 
-        if (this._touchGesture) {
-            global.stage.remove_action(this._touchGesture);
-            delete this._touchGesture;
+        if (this._panGesture) {
+            this._panGesture.actor?.remove_action(this._panGesture);
+            delete this._panGesture;
         }
     }
 });